part of 'win.dart';

class BluetoothCharacteristicWindows extends BluetoothCharacteristic {
  final DeviceIdentifier remoteId;
  final Guid serviceUuid;
  final Guid? secondaryServiceUuid;
  final Guid characteristicUuid;
  final List<BluetoothDescriptor> descriptors;

  final CharacteristicProperties propertiesWinBle;

  static BluetoothDescriptor fromJsonDescriptor(DeviceIdentifier remoteId, Guid serviceUuid, Guid characteristicUuid, Guid descriptorUuid) {
    return BluetoothDescriptor.fromProto(
        BmBluetoothDescriptor(remoteId: remoteId, serviceUuid: serviceUuid, characteristicUuid: characteristicUuid, descriptorUuid: descriptorUuid, primaryServiceUuid: null));
  }

  static CharacteristicProperties fromStringProperties(String s) {
    return CharacteristicProperties.fromProto(BmCharacteristicProperties(
        broadcast: s.contains('broadcast'),
        read: s.contains('read'),
        writeWithoutResponse: s.contains('writeWithoutResponse'),
        write: s.contains('write'),
        notify: s.contains('notify'),
        indicate: s.contains('indicate'),
        authenticatedSignedWrites: s.contains('authenticatedSignedWrites'),
        extendedProperties: s.contains('extendedProperties'),
        notifyEncryptionRequired: false,
        indicateEncryptionRequired: false));
  }

  static BluetoothCharacteristicWindows fromJson(Map map) {
    final remoteId = DeviceIdentifier(map['deviceId']);
    final serviceUuid = Guid(map['serviceUuid']);
    final characteristicUuid = Guid(map['uuid']);
    return BluetoothCharacteristicWindows(
      remoteId: remoteId,
      serviceUuid: serviceUuid,
      characteristicUuid: characteristicUuid,
      descriptors: (map['descriptors'] as List?)?.map((it) => fromJsonDescriptor(remoteId, serviceUuid, characteristicUuid, Guid(it))).toList() ?? <BluetoothDescriptor>[],
      propertiesWinBle: fromStringProperties(map['properties']),
      secondaryServiceUuid: null,
    );
  }

  BluetoothCharacteristicWindows({
    required this.remoteId,
    required this.serviceUuid,
    required this.characteristicUuid,
    required this.descriptors,
    required this.propertiesWinBle,
    this.secondaryServiceUuid,
  }) : super.fromProto(
          BmBluetoothCharacteristic(
            remoteId: DeviceIdentifier(remoteId.str),
            serviceUuid: serviceUuid,
            // secondaryServiceUuid: secondaryServiceUuid,
            characteristicUuid: characteristicUuid,
            descriptors: [
              for (final descriptor in descriptors)
                BmBluetoothDescriptor(
                  remoteId: DeviceIdentifier(descriptor.remoteId.str),
                  serviceUuid: descriptor.serviceUuid,
                  characteristicUuid: descriptor.characteristicUuid,
                  descriptorUuid: descriptor.uuid,
                  primaryServiceUuid: null, // TODO:  API changes
                ),
            ],
            properties: BmCharacteristicProperties(
              broadcast: propertiesWinBle.broadcast,
              read: propertiesWinBle.read,
              writeWithoutResponse: propertiesWinBle.writeWithoutResponse ?? false,
              write: propertiesWinBle.write,
              notify: propertiesWinBle.notify,
              indicate: propertiesWinBle.indicate,
              authenticatedSignedWrites: propertiesWinBle.authenticatedSignedWrites ?? false,
              extendedProperties: false,
              notifyEncryptionRequired: false,
              indicateEncryptionRequired: false,
            ),
            primaryServiceUuid: null, // TODO:  API changes
          ),
        );
  bool _notifying = false;
  List<int> _value = [];

  bool _valueIsMe(BaseCharacteristicData e) => e.deviceId == remoteId && e.serviceUuid == serviceUuid && e.characteristicUuid == characteristicUuid;

  @override
  CharacteristicProperties get properties => propertiesWinBle;

  FBP.BluetoothDevice get device => FlutterBluePlusWin._devices[remoteId] ?? BluetoothDeviceWindows(remoteId: remoteId);

  /// 此变量已更新：
  ///-任何时候调用read（）
  ///-任何时候调用`write（）`
  ///-通知到达时（如果订阅）
  List<int> get lastValue => _value;

  /// 该流发出值：
  ///-任何时候调用read（）
  ///-任何时候调用`write（）`
  ///-通知到达时（如果订阅）
  ///-首次收听时，为了方便起见，它会重新发出最后一个值
  Stream<List<int>> get lastValueStream =>
      FlutterBluePlusWin._charReadNotificationStream.where((e) => _valueIsMe(e)).map((e) => e.value.toList()).newStreamWithInitialValue(lastValue).asBroadcastStream();

  ///该流发出值：
  ///-任何时候调用read（）
  ///-通知到达时（如果订阅）
  Stream<List<int>> get onValueReceived => FlutterBluePlusWin._charReadNotificationStream
      .where((e) => _valueIsMe(e) && (e.type == ValueChangeType.read || e.type == ValueChangeType.notify))
      .map((e) => e.value.toList())
      .asBroadcastStream();

  bool get isNotifying => _notifying;

  Future<List<int>> read({int timeout = 15}) async {
    try {
      //原生交互
      await WinMethodChannelBlue.instance.readValue(remoteId, serviceUuid, characteristicUuid);
      //等待回调
      return FlutterBluePlusWin._charReadNotificationStream.where((it) => _valueIsMe(it)).map((it) => it.value.toList()).timeout(Duration(seconds: timeout)).first;
    } catch (e) {
      log("read error $this");
    }
    return [];
  }

  Future<void> write(List<int> value, {bool allowLongWrite = false, bool withoutResponse = false, int timeout = 15}) async {
    try {
      //原生交互
      await WinMethodChannelBlue.instance
          .writeValue(remoteId, serviceUuid, characteristicUuid, Uint8List.fromList(value), withoutResponse ? BleOutputProperty.withoutResponse : BleOutputProperty.withResponse);
    } catch (e) {
      log("wrote error $this");
    }
    return;
  }

  Future<bool> setNotifyValue(
    bool notify, {
    int timeout = 15,
    bool forceIndications = false,
  }) async {
    try {
      //原生交互
      await WinMethodChannelBlue.instance
          .setNotifiable(remoteId, serviceUuid, characteristicUuid, forceIndications ? BleInputProperty.indication : (notify ? BleInputProperty.notification : BleInputProperty.disabled));
      //等待通知结果
      final result = await FlutterBluePlusWin._setNotifyResultStream.where((it) => _valueIsMe(it)).timeout(Duration(seconds: timeout)).first;
      if (result.success) {
        _notifying = result.property == BleInputProperty.notification;
        return true;
      }
      return false;
    } catch (e) {
      log('WinBle.unSubscribeFromCharacteristic was performed '
          'before setNotifyValue()');
    }
    return false;
  }
}

class BleInputProperty {
  static const disabled = BleInputProperty._('disabled');
  static const notification = BleInputProperty._('notification');
  static const indication = BleInputProperty._('indication');

  final String value;

  const BleInputProperty._(this.value);

  @override
  String toString() => value;

  //从一个String 转换成 BleInputProperty
  static BleInputProperty fromString(String value) {
    if (value == disabled.value) return disabled;
    if (value == notification.value) return notification;
    if (value == indication.value) return indication;
    throw ArgumentError('Unknown BleInputProperty value: $value');
  }
}

class BleOutputProperty {
  static const withResponse = BleOutputProperty._('withResponse');
  static const withoutResponse = BleOutputProperty._('withoutResponse');

  final String value;

  const BleOutputProperty._(this.value);
}
